using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;

using Asp.Versioning.ApplicationModels;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore;

using Swashbuckle.AspNetCore.SwaggerGen;

using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Swashbuckle;

using WL.Think.Abp.Extensions;
using WL.Think.Abp.Formatters;

namespace WL.Think.Abp.Modules;

public abstract class ThinkStartupModule<TModule>: ThinkModule<TModule> where TModule : ThinkModule<TModule> 
{
    /// <summary>
    /// 预配置OpenIddict服务
    /// </summary>
    /// <param name="context"></param>
    /// <exception cref="FileNotFoundException"></exception>
    protected override void PreConfigureOpenIddict(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();

        context.Services.PreConfigure<OpenIddictBuilder>(builder =>
        {
            builder.AddValidation(opts =>
            {
                opts.UseLocalServer();
                opts.UseAspNetCore();
                /*
                   iss，令牌的颁发者，其值就是身份认证服务（OP）的 URL。
                   sub，令牌的主题，其值是一个能够代表最终用户（EU）的全局唯一标识符。
                   aud，令牌的目标受众，其值是三方软件（RP）的 app_id。
                   exp，令牌的到期时间戳，所有的 ID 令牌都会有一个过期时间。
                   iat，颁发令牌的时间戳。
                */
                opts.AddAudiences(configuration["AuthServer:Audiences"]!);
            });
        });

        if (!hostingEnvironment.IsDevelopment())
        {
            context.Services.PreConfigure<OpenIddictServerBuilder>(builder =>
            {
                var fileName = "openiddict.pfx";
                var passPhrase = configuration["AuthServer:CertificatePassPhrase"]!;

                if (!File.Exists(fileName))
                {
                    throw new FileNotFoundException($"Signing Certificate couldn't found: {fileName}");
                }

                var certificate = X509CertificateLoader.LoadPkcs12FromFile(fileName, passPhrase);
                builder.AddSigningCertificate(certificate);
                builder.AddEncryptionCertificate(certificate);
                builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
            });
        }
    }

    /// <summary>
    /// 配置OpenIddict参数
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureOpenIddict(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();
        if (!configuration.GetValue("App:DisablePII", true))
        {
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.LogCompleteSecurityArtifact = true;
        }

        if (configuration.GetValue("AuthServer:RequireHttpsMetadata", false))
        {
            return;
        }

        context.Services.Configure<OpenIddictServerAspNetCoreOptions>(options =>
            options.DisableTransportSecurityRequirement = true);

        context.Services.Configure<ForwardedHeadersOptions>(options =>
            options.ForwardedHeaders = ForwardedHeaders.XForwardedProto);
    }

    /// <summary>
    /// 配置认证信息
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureAuthentication(ServiceConfigurationContext context)
    {
        context.Services.ConfigureApplicationCookie(options =>
        {
            options.ForwardDefaultSelector = ctx =>
            {
                string? authorization = ctx.Request.Headers.Authorization;
                if (!authorization.IsNullOrWhiteSpace() && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                {
                    return OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
                }

                return null;
            };
        });

        context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
        {
            options.IsDynamicClaimsEnabled = true;
        });
    }

    /// <summary>
    /// 配置JSON参数
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureJson(ServiceConfigurationContext context)
    {
        Configure<JsonOptions>(opts =>
        {
            opts.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
            opts.SerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
            opts.SerializerOptions.WriteIndented = false;
            opts.SerializerOptions.Converters.Add(new DateTimeJsonConverter());
            //JsonObjectCreationHandling 默认值在 .NET 8 中从 Replace 改为了 Populate。
            //Populate 行为对于反序列化到现有对象（例如在 MVC 模型绑定中）非常有用，但它与“引用循环处理”不兼容
            //opts.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        });

        Configure<AbpSystemTextJsonSerializerOptions>(opts =>
        {
            opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
            opts.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
            opts.JsonSerializerOptions.WriteIndented = false;
            opts.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
            //JsonObjectCreationHandling 默认值在 .NET 8 中从 Replace 改为了 Populate。
            //Populate 行为对于反序列化到现有对象（例如在 MVC 模型绑定中）非常有用，但它与“引用循环处理”不兼容
            //opts.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        });

    }



    /// <summary>
    /// 配置控制器参数
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureControllers(ServiceConfigurationContext context)
    {
        context.Services
            .AddControllers(opts => {
                opts.InputFormatters.Add(new RawRequestBodyFormatter());
            })
            .AddJsonOptions(opts => {
                opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                opts.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
                opts.JsonSerializerOptions.WriteIndented = false;
                opts.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
                //JsonObjectCreationHandling 默认值在 .NET 8 中从 Replace 改为了 Populate。
                //Populate 行为对于反序列化到现有对象（例如在 MVC 模型绑定中）非常有用，但它与“引用循环处理”不兼容
                //opts.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
            });

    }

    /// <summary>
    /// 配置健康检查
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureHealthChecks(ServiceConfigurationContext context)
    {
        context.Services.AddHealthChecks();
    }

    /// <summary>
    /// 配置接口文档
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureSwagger(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();
        context.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();

        context.Services.AddAbpSwaggerGenWithOidc(
            configuration["AuthServer:Authority"]!,
            [configuration["AuthServer:Scope"]!],
            [AbpSwaggerOidcFlows.AuthorizationCode],
            null,
            options =>
            {
                options.OperationFilter<SwaggerDefaultValues>();
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            });
    }

    /// <summary>
    /// 配置API版本管理
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureApiVersion(ServiceConfigurationContext context)
    {
        var preActions = context.Services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>();
        context.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
        context.Services.AddAbpApiVersioning(options =>
        {
            options.ReportApiVersions = true;
            options.AssumeDefaultVersionWhenUnspecified = true;
        }, options =>
        {
            options.ConfigureAbp(preActions.Configure());
        }
        )
        .AddApiExplorer(options =>
        {
            options.GroupNameFormat = "'v'VVV";
            options.SubstituteApiVersionInUrl = true;
        });

        context.Services.Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            preActions.Configure(options);
            options.ChangeControllerModelApiExplorerGroupName = false;
        });
    }

    /// <summary>
    /// 配置跨域
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureCors(ServiceConfigurationContext context)
    {
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder.WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod();

                var corOrigins = context.Configuration["App:CorsOrigins"];
                if (string.IsNullOrWhiteSpace(corOrigins))
                {
                    builder.AllowAnyOrigin();
                }
                else
                {
                    builder.WithOrigins(
                        corOrigins?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.Trim().RemovePostFix("/"))
                            .ToArray() ?? []
                    ).AllowCredentials();
                }
            });
        });
    }

    /// <summary>
    /// 配置跨站请求伪造
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureAntiForgery(ServiceConfigurationContext context)
    {
        context.Services.Configure<AbpAntiForgeryOptions>(opts =>
        {
            /*
             * 当Cookie值为HttpOnly时, 浏览器无法通过JS的 document.cookies 访问,
             * 同时当 Cookie 的SameSite值为None时, 但是Secure不是 Always, 浏览器无法通过JS的 document.cookies 访问,
             * 这个是为了解决当网站部署为HTTP时, Swagger无法获取到 XSRF-TOKEN 的问题, 由于无法获取到导致 POST,PUT,DELETE 请求报400错误
             * 当能获取到 XSRF-TOKEN 时, 会报这个值添加到 Header 的 RequestVerificationToken 中
             *
             * 如果是HTTPS, 则不会有这个问题
             */
            opts.TokenCookie.SecurePolicy = CookieSecurePolicy.Always;
        });
    }

    /// <summary>
    /// 配置多租户
    /// </summary>
    /// <param name="context"></param>
    protected override void ConfigureMultiTenancy(ServiceConfigurationContext context)
    {
        var enableMultiTenancy = context.Configuration.GetValue("App:MultiTenancy", false);
        context.Services.Configure<AbpMultiTenancyOptions>(opts =>
        {
            opts.IsEnabled = enableMultiTenancy;
        });
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context) 
    {
        var app = context.GetApplicationBuilder();
        app.UseForwardedHeaders();
        app.UseException();
        app.UseAbpRequestLocalization();
        app.UseCorrelationId();
        app.UseRouting();
        app.MapAbpStaticAssets();
        app.UseAbpSecurityHeaders();
        app.UseCors();
        app.UseAntiforgery();
        app.UseAuthentication();
        app.UseOpenIddictValidation();
        app.UseMultiTenant();
        app.UseUnitOfWork();
        app.UseDynamicClaims();
        app.UseAuthorization();
        app.UseSwaggerAndUI();
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints(MapEndpoints);
    }

    /// <summary>
    /// 映射终结点
    /// </summary>
    /// <param name="endpoints"></param>
    protected abstract void MapEndpoints(IEndpointRouteBuilder builder);
}
